<?php
class Slash_ACL_Property_Acl extends Sabre_DAV_Property 
{
	/*
   * Copyright © 2010 Jack Cleaver
   * 
   * This plugin provides functionality added by CalDAV (RFC 4791)
   * It implements new reports, and the MKCALENDAR method.
   *
   * This file is part of Slash.
   *
   * Slash is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * Slash is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with Slash.  If not, see <http://www.gnu.org/licenses/>.
	 */

  static $prefix = "{DAV:}";
	
  /*
   * Two arrays (grants, denies) indexed by a principal string, with the value as an array of privs.
   * The principal is a ACE principal element expressed in Clark notation.
   *
   * $aces is an array of princstr=>array(
   * 		"privs"=>array(privs),
   * 		"protected"=>boolean,
   * 		"grant"=>boolean
   *  ).
   * Thus each element of $aces represents a single ACE; it confers/denies a set of privs on a 
   * single principal, optionally marking it as protected.
   * A principal may have more than one ACE; for example:
   * 	grant read
   * 	deny write-acl;
   *  grant all.
   * What I want to support is a grant-only system.
   * 
   * The princstr key is as follows:
   * self:
   * authenticated:
   * unauthenticated:
   * all:
   * href:/principals/xyz
   */ 
  public $name;
	private $aces = array();
	
	public static $nextId = 0;
	public $id;
	
	public function __construct($name)
	{		
		$this->name = $name;
	}

	public function grant(Slash_ACL_Property_Principal $principal, $privnames, $protected)
	{
		$princStr = $principal->toString();
		Slash_Debug::debug("ACL Property Acl grant: principal=" . $princStr);
		$ace = array();		
		$ace["privs"] = $privnames;
		$ace["protected"] = $protected;
		$ace["grant"] = true;
		$this->aces[$princStr][] = $ace;
	}
	
	public function deny(Slash_ACL_Property_Principal $principal, $privnames, $protected)
	{
		Slash_Debug::debug("ACL Property Acl deny: principal=" . $principal->toString());
		$princStr = $principal->toString();
		$ace = array();		
		$ace["privs"] = $privnames;
		$ace["protected"] = $protected;
		$ace["grant"] = false;
		$this->aces[$princStr][] = $ace;
	}
	
	
	public function isValidPrincStr($princStr)
	{
		$parts = explode(":", $princStr, 2);
		if (strlen($parts[1]) > 0)
		{
			//Slash_Debug::debug("ACL_Property_Acl isValidPrincStr: $extra for $princStr");
			if ($parts[0] == "property")
			{
				return preg_match("/{([^}]+)}(.+)/", $parts[1]);
			} else if ($parts[0] == "href")
			{
				return (strlen($parts[1]) > 0);
			}
		} else {
			Slash_Debug::debug("ACL_Property_Acl isValidPrincStr: base for $princStr");
			return isset($parts[0]);
		}
	}
	
	
	/*
	 * Concatenate the text-node descendants of an element. 
	 */
	private function textValue($elem)
	{
		$result = ""; 
		$children = $elem->childNodes;
		$len = $children->length;
		for ($i = 0; $i < $len; $i++)
		{
			$child = $children->item($i);
			if ($child->nodeType == XML_ELEMENT_NODE)
			{
				$result .= $this->textValue($child);
			} else if ($child->nodeType == XML_TEXT_NODE)
			{
				$result .= $child->nodeValue;
			}
		}
		return $result;
	}
	
	/*
	 * convert the contents of a principal element to a string in Clark notation;
	 * e.g. 
	 * <{DAV:}href>x/y/z</{DAV:}href>
	 * <{DAV:}owner />
	 */
	public function clarkPrincipal($principal)
	{
		$principal->normalize();
		$first = $principal->getElementsByTagName("*")->item(0);
		//$children = $principal->childNodes;
		//Slash_Debug::debug("ACL clarkPrincipal: numchildren = " . $principal->childNodes->length);
		$clark = Sabre_DAV_XMLUtil::toClarkNotation($first);
		if ($clark == "{DAV:}href")
		{
			return "<$clark>" . $this->textValue($principal) . "</$clark>";
		} else {
			return "<$clark />";
		}
	}
	
	public function getAces()
	{
		return $this->aces;
	}
	
	/*
	 * convert a string in Clark notation into a principal element.
	 * TODO: this currently doesn't do what it says.
	 */
	public function principalClark($dom, $prefix, $str)
	{
		$princ = $dom->createElement("$prefix:principal");
		$e = strtok($str, "<>");
		//Slash_Debug::debug("str is $str, e is $e");
		$clark = Sabre_DAV_XMLUtil::toClarkNotation($first);
		if ($e == "{DAV:}href")
		{
			//$href = $dom->createElementNS()
			return "<$clark>" . $first->firstChild->nodeValue() . "</$clark>";
		} else {
			return "<$clark />";
		}
	}
	
	public static function unserialize(DOMElement $aclElement)
	{
		Slash_Debug::debug("ACL unserialize");
		$acl = new self("Unserialized");
		$xml = $aclElement->ownerDocument->saveXML();
		$nlAce = $aclElement->getElementsByTagNameNS("urn:DAV", "ace");
		$numAce = $nlAce->length;
		Slash_Debug::debug("ACL unserialize: xml=$xml; num ACEs=$numAce");
		for ($i = 0; $i < $numAce; $i++)
		{
			$ace = $nlAce->item($i);
			$princ = $ace->getElementsByTagNameNS("urn:DAV", "principal")->item(0);
			$princProp = Slash_ACL_Property_Principal::unserialize($princ);
			$nlProt = $ace->getElementsByTagNameNS("urn:DAV", "protected");
			$protected = ($nlProt->length > 0);
			$grant = $ace->getElementsByTagNameNS("urn:DAV", "grant")->item(0);
			$deny = $ace->getElementsByTagNameNS("urn:DAV", "deny")->item(0);
			$privs = array();
			if ($grant)
			{
				$gprivs = $grant->getElementsByTagNameNS("urn:DAV", "privilege");
				$len = $gprivs->length;
				for ($j = 0; $j < $len; $j++)
				{
					$priv = $gprivs->item($j)->getElementsByTagName("*")->item(0)->localName;
					//Slash_Debug::debug("ACL unserialize: grant to " . $princProp->toString() . " priv=$priv");
					$privs[] = $priv;
				}
				$acl->grant($princProp, $privs, $protected);
			}
			if ($deny)
			{
				$dprivs = $deny->getElementsByTagNameNS("urn:DAV", "privilege");
				$len = $dprivs->length;
				for ($j = 0; $j < $len; $j++)
				{
					$priv = $dprivs->item($j)->getElementsByTagName("*")->item(0)->localName;
					$privs[] = $priv;
				}
				$acl->deny($princProp, $privs, $protected);
			}
		}
    //Slash_Debug::debugDump("in Acl unserialize: grants=", $acl->grants);
		return $acl;
	}
	
	
	/*
	 * Return true if two ACEs are identical, else false.
	 */
	private function compareAces($ace1, $ace2)
	{
		$result = "";
		if ($ace1["protected"] != $ace2["protected"])
		{
			$result = "Not both protected";
		} else if ($ace1["grant"] != $ace2["grant"])
		{
			$result = "Actions don't match";
		} else {
			foreach($ace1["privs"] as $priv)
			{
				if (!in_array($priv, $ace2["privs"]))
				{
					$result = "Privilege $priv is not present";
					break;
				}
			}
			foreach($ace2["privs"] as $priv)
			{
				if (!in_array($priv, $ace1["privs"]))
				{
					$result = "Privilege $priv should not be present";
					break;
				}
			}
		}
		return $result;
	}
			
		
	public function conflictsWith(Slash_ACL_Property_Acl $acl)
	{
		$result = false;
		
		$newAces = $acl->getAces();		
		/*
		 * Check each of my ACEs, and if it's protected, check that it's present 
		 * and protected in the new version.
		 */  
		foreach ($this->aces as $princStr=>$thisAces)
		{
			foreach ($thisAces as $thisAce)
			{
				if (isset($newAces[$princStr]))
				{
					$thatAces = $newAces[$princStr];
					foreach ($thatAces as $thatAce)
					{
						if ($thisAce["protected"])
						{
							$str = $this->compareAces($thisAce, $thatAce); 							
							if (strlen($str) > 0)
							{
								$result = "ACEs differ for $princStr: $str";
								break 3;
							}
						}
					}
				}
			}
		}
		return $result;
	}
				
			
	
	/*
	 * Converts a Property into a DOMElement.
	 * Looks like:
	 *<acl>
	 * 	<ace>
	 * 		<principal><href>gfdgdgdg</href></principal>
	 * 		<grant>
	 * 			<privilege><privname /></privilege>
	 *		</grant>
	 *	</ace>
	 *</acl>
	 
	 
	 */
	public function serialize(Sabre_DAV_Server $server, DOMElement $aclElement) 
	{
		$prefix = $server->xmlNamespaces['DAV:'];
				
		$doc = $aclElement->ownerDocument;
		
    //Slash_Debug::debugDump("in Acl serialize: grants=", $this->grants);
		foreach ($this->aces as $princStr=>$princAces)
    {
			Slash_Debug::debug("ACL serialize: num aces for $princStr=" . count($princAces));
			foreach ($princAces as $princAce)
			{
				Slash_Debug::debug("ACL_Property_Acl serialize: $princStr");
				$princProp = Slash_ACL_Property_Principal::fromString($princStr);
    		$princElement = $doc->createElement("$prefix:principal");
    		$princProp->serialize($server, $princElement);
    		
    		$ace = $doc->createElement("$prefix:ace");
    		
    		if ($princAce["protected"])
    		{
    			$eProt = $doc->createElement("$prefix:protected");
    			$ace->appendChild($eProt);
    		}    			

    		if ($princAce["grant"])
				{
					$action = $doc->createElement("$prefix:grant");
				} else {
					$action = $doc->createElement("$prefix:deny");
				}
    		$ace->appendChild($princElement);
    		
    		$privs = $princAce["privs"];
				foreach($privs as $privname)
    		{
    			$privilege = $doc->createElement("$prefix:privilege");
    			$priv = $doc->createElement("$prefix:$privname");
    			$privilege->appendChild($priv);
    			$action->appendChild($privilege);
    		}
    		$ace->appendChild($action);
    	}
    	$aclElement->appendChild($ace);    	
    }
	}
}